"""
==============================================================
#   author：段老师  (1036198123@qq.com)
#   time：2020.12.6
#   filename：桌面小主1.0.py
#   ver：V1.0
#   des：定制你的语音桌面小主
#   GPL License （欢迎魔改，但是该程序所有修订及拷贝版本都需要开源。QT部分程序参考自微信公众号：Charles的皮卡丘）
#   Copyright (c) 2020 段老师
#   Python 3.7 运行正常
==============================================================
"""
import io              
import wave
import pyaudio    #PyAudio 可以直接下载Wheel安装包  https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyaudio 找对应的版本 通过pip install 文件名(通过链接下载的)
import os
import sys
import random
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5 import QtWidgets, QtGui
import websocket
import datetime
import hashlib
import base64
import hmac
import json
from urllib.parse import urlencode
import time
import ssl
from wsgiref.handlers import format_date_time
from datetime import datetime
from time import mktime
import _thread as thread

global wsParam2


# 调用讯飞语音合成API的类
class Ws_Param2(object):
    # 初始化
    def __init__(self, APPID, APIKey, APISecret, Text):
        self.APPID = APPID
        self.APIKey = APIKey
        self.APISecret = APISecret
        self.Text = Text
        # 公共参数(common)
        self.CommonArgs = {"app_id": self.APPID}
        # 业务参数(business)，更多个性化参数可在官网查看，可以更换不同的人声
        self.BusinessArgs = {
            "aue": "raw", "auf": "audio/L16;rate=16000", "vcn": "xiaoyan", "tte": "utf8"}
        self.Data = {"status": 2, "text": str(
            base64.b64encode(self.Text.encode('utf-8')), "UTF8")}
        # 使用小语种须使用以下方式，此处的unicode指的是 utf16小端的编码方式，即"UTF-16LE"”
        # self.Data = {"status": 2, "text": str(base64.b64encode(self.Text.encode('utf-16')), "UTF8")}

    # 生成url

    def create_url(self):
        url = 'wss://tts-api.xfyun.cn/v2/tts'
        # 生成RFC1123格式的时间戳
        now = datetime.now()
        date = format_date_time(mktime(now.timetuple()))
        # 拼接字符串
        signature_origin = "host: " + "ws-api.xfyun.cn" + "\n"
        signature_origin += "date: " + date + "\n"
        signature_origin += "GET " + "/v2/tts " + "HTTP/1.1"
        # 进行hmac-sha256进行加密
        signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin.encode('utf-8'),
                                 digestmod=hashlib.sha256).digest()
        signature_sha = base64.b64encode(
            signature_sha).decode(encoding='utf-8')

        authorization_origin = "api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"" % (
            self.APIKey, "hmac-sha256", "host date request-line", signature_sha)
        authorization = base64.b64encode(
            authorization_origin.encode('utf-8')).decode(encoding='utf-8')
        # 将请求的鉴权参数组合为字典
        v = {
            "authorization": authorization,
            "date": date,
            "host": "ws-api.xfyun.cn"
        }
        # 拼接鉴权参数，生成url
        url = url + '?' + urlencode(v)
        # print("date: ",date)
        # print("v: ",v)
        # 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释，比对相同参数时生成的url与自己代码生成的url是否一致
        # print('websocket url :', url)
        return url


def on_message2(ws2, message):
    try:
        message = json.loads(message)
        code = message["code"]
        sid = message["sid"]
        audio = message["data"]["audio"]
        audio = base64.b64decode(audio)
        status = message["data"]["status"]
        # print(message)
        if status == 2:
            print("结束讯飞AI调用")
            ws2.close()
        if code != 0:
            errMsg = message["message"]
            print("sid:%s call error:%s code is:%s" % (sid, errMsg, code))
        else:

            with open('output.pcm', 'ab') as f:
                f.write(audio)
            pcm2wav("output.pcm", "output.wav")
    except Exception as e:
        print("receive msg,but parse exception:", e)


# 收到websocket错误的处理
def on_error2(ws2, error):
    print("### error:", error)


# 收到websocket关闭的处理
def on_close2(ws2):
    print("### closed ###")


# 收到websocket连接建立的处理
def on_open2(ws2):
    def run(*args):
        global wsParam2
        d = {"common": wsParam2.CommonArgs,
             "business": wsParam2.BusinessArgs,
             "data": wsParam2.Data,
             }
        d = json.dumps(d)
        # print("------>开始发送文本数据")
        ws2.send(d)
        if os.path.exists('output.pcm'):
            os.remove('output.pcm')

    thread.start_new_thread(run, ())

# 播放wav音频文件
def playwav():
    chunk = 1024
    wf = wave.open('output.wav', 'rb')
    p = pyaudio.PyAudio()
    stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
    channels=wf.getnchannels(),
    rate=wf.getframerate(),
    output=True)
    data = wf.readframes(chunk)
    while len(data) > 0:
        stream.write(data)
        data = wf.readframes(chunk)
    stream.stop_stream()
    stream.close()
    p.terminate()
# 将pcm加入头文件组成完整的wav音频文件
def pcm2wav(pcm_file, save_file, channels=1, bits=16, sample_rate=16000):
        pcmf = open(pcm_file, 'rb')
        pcmdata = pcmf.read()
        pcmf.close()
        if bits % 8 != 0:
             raise ValueError("bits % 8 must == 0. now bits:" + str(bits))
        wavfile = wave.open(save_file, 'wb')
        wavfile.setnchannels(channels)
        wavfile.setsampwidth(bits//8)
        wavfile.setframerate(sample_rate)
        wavfile.writeframes(pcmdata)
        wavfile.close()

def speak(atext):
    global wsParam2
    wsParam2 = Ws_Param2(APPID='5f09e790', APIKey='69000af7b9794e39688630d4a62002e5',#请注册你自己的科大讯飞账号，这个账号是笔者的有限，用的时候悠着点。
    APISecret='e88504cabbb8646417da0ee975ed84e3',
    Text=atext)
    #websocket.enableTrace(False)
    wsUrl2 = wsParam2.create_url()
    ws2 = websocket.WebSocketApp(wsUrl2, on_message=on_message2, on_error=on_error2, on_close=on_close2)
    ws2.on_open = on_open2
    ws2.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
    playwav() 

'''桌面小主'''
class DesktopPet(QWidget):
    def __init__(self, parent=None, **kwargs):
            super(DesktopPet, self).__init__(parent)
            # 初始化
            self.setWindowFlags(Qt.FramelessWindowHint |
                                Qt.WindowStaysOnTopHint | Qt.SubWindow)
            self.setAutoFillBackground(False)
            self.setAttribute(Qt.WA_TranslucentBackground, True)
            self.repaint()
            # 随机导入一个你的桌面小主
            self.pet_images, iconpath = self.LoadPetImages()
            # 设置退出选项
            quit_action = QAction('退出', self, triggered=self.quit)
            quit_action.setIcon(QIcon(iconpath))
            self.tray_icon_menu = QMenu(self)
            self.tray_icon_menu.addAction(quit_action)
            self.tray_icon = QSystemTrayIcon(self)
            self.tray_icon.setIcon(QIcon(iconpath))
            self.tray_icon.setContextMenu(self.tray_icon_menu)
            self.tray_icon.show()
            # 当前显示的图片
            self.image = QLabel(self)
            self.setImage(self.pet_images[0])
            # 是否跟随鼠标
            self.is_follow_mouse = False
            # 宠物拖拽时避免鼠标直接跳到左上角
            self.mouse_drag_pos = self.pos()
            # 显示
            self.resize(281, 468)
            self.randomPosition()
            self.show()
            # 宠物动画动作执行所需的一些变量
            self.is_running_action = False
            self.action_images = []
            self.action_pointer = 0
            self.action_max_len = 0
            # 每隔一段时间做个动作
            self.timer = QTimer()
            self.timer.timeout.connect(self.randomAct)
            self.timer.start(50)
    '''随机做一个动作'''

    def randomAct(self):
            if not self.is_running_action:
                    self.is_running_action = True
                    self.action_images = self.pet_images
                    self.action_max_len=65
                    self.action_pointer = 0
            self.runFrame()
    '''完成动作的每一帧'''

    def runFrame(self):
            if self.action_pointer == self.action_max_len:
                    self.is_running_action = False
                    self.action_pointer = 0
                    self.action_max_len = 0
            self.setImage(self.loadImage("./img/"+'图层 '+str(self.action_pointer+1)+'.png'))
            self.action_pointer += 1
    '''设置当前显示的图片'''
    def setImage(self,image):
            self.image.setPixmap(QPixmap.fromImage(image))
    def LoadPetImages(self):
                pet_images = []
                for i in (1, 66):
                   pet_images.append(self.loadImage("./img/"+'图层 '+str(i)+'.png'))
                print(pet_images)
                iconpath = os.path.join("./img/", '图层 1.png')                
                return pet_images,iconpath
    def mousePressEvent(self,event):
        if event.button() == Qt.LeftButton:
            self.is_follow_mouse = True
            self.mouse_drag_pos = event.globalPos() - self.pos()
            event.accept()
            self.setCursor(QCursor(Qt.OpenHandCursor))
        elif event.button() == Qt.RightButton:
            self.setImage(self.loadImage("./img/speak.png"))
            speak("主人您好，您可以为我添加更多功能。做的如何就看您的水平了。")
        '''鼠标移动, 则小主也移动'''
    def mouseMoveEvent(self, event):
            if Qt.LeftButton and self.is_follow_mouse:
                    self.move(event.globalPos() - self.mouse_drag_pos)
                    event.accept()
    '''鼠标释放时, 取消绑定'''
    def mouseReleaseEvent(self, event):
            self.is_follow_mouse = False
            self.setCursor(QCursor(Qt.ArrowCursor))
    '''导入图像'''
    def loadImage(self, imagepath):
            image = QImage()
            image.load(imagepath)
            return image
    '''随机到一个屏幕上的某个位置'''
    def randomPosition(self):
            screen_geo = QDesktopWidget().screenGeometry()
            pet_geo = self.geometry()
            width = (screen_geo.width() - pet_geo.width()) * random.random()
            height = (screen_geo.height() - pet_geo.height()) * random.random()
            self.move(width, height)
    '''退出程序'''
    def quit(self):
            self.close()
            sys.exit()



'''run'''
if __name__ == '__main__':
	app = QApplication(sys.argv)
	pet = DesktopPet()
	sys.exit(app.exec_())
